/*
Copyright (C) 2002 Rice1964
Copyright (C) 2009-2011 Richard Goedeken

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include <vector>
#include <stdarg.h>
#include <SDL_opengl.h>

extern "C" {
#include "m64p_types.h"
#include "m64p_common.h"
#include "m64p_device.h"
}

#include "Config.h"
#include "Debugger.h"
#include "DeviceBuilder.h"
#include "FrameBuffer.h"
#include "GraphicsContext.h"
#include "Render.h"
#include "RSP_Parser.h"
#include "TextureFilters.h"
#include "TextureManager.h"
#include "Video.h"

static void (*l_DebugCallback)(void *, int, const char *) = NULL;
static void *l_DebugCallContext = NULL;
static int l_PluginInit = 0;

PluginStatus  status;
CCritSect     g_CritialSection;

unsigned int   g_dwRamSize = 0x400000;
unsigned int  *g_pRDRAMu32 = NULL;
signed char   *g_pRDRAMs8 = NULL;
unsigned char *g_pRDRAMu8 = NULL;

RECT frameWriteByCPURect;
std::vector<RECT> frameWriteByCPURects;
RECT frameWriteByCPURectArray[20][20];
bool frameWriteByCPURectFlag[20][20];
std::vector<uint32> frameWriteRecord;

#ifdef __cplusplus
extern "C" {
#endif

void ProcessRDPList(void);
void UpdateScreen(void);
void ChangeWindow (void);
void MoveScreen (int xpos, int ypos);
void UpdateScreen(void);
void ViStatusChanged(void);
void ViWidthChanged(void);
void ProcessRDPList(void);
void ProcessDList(void);
void FBRead(uint32 addr);
void FBWrite(uint32 addr, uint32 size);
void FBGetFrameBufferInfo(void *p);
void ShowCFB(void);
void ReadScreen2(void *dest, int *width, int *height, int bFront);
void SetRenderingCallback(void (*callback)(int));

#ifdef __cplusplus
}
#endif

void (*renderCallback)(int) = NULL;

static void ChangeWindowStep2()
{
    status.bDisableFPS = true;
    windowSetting.bDisplayFullscreen = !windowSetting.bDisplayFullscreen;
    g_CritialSection.Lock();
    windowSetting.bDisplayFullscreen = CGraphicsContext::Get()->ToggleFullscreen();

    CGraphicsContext::Get()->Clear(CLEAR_COLOR_AND_DEPTH_BUFFER);
    CGraphicsContext::Get()->UpdateFrame();
    CGraphicsContext::Get()->Clear(CLEAR_COLOR_AND_DEPTH_BUFFER);
    CGraphicsContext::Get()->UpdateFrame();
    CGraphicsContext::Get()->Clear(CLEAR_COLOR_AND_DEPTH_BUFFER);
    CGraphicsContext::Get()->UpdateFrame();
    g_CritialSection.Unlock();
    status.bDisableFPS = false;
    status.ToToggleFullScreen = FALSE;
}

static void UpdateScreenStep2 (void)
{
    status.bVIOriginIsUpdated = false;

    if( status.ToToggleFullScreen && status.gDlistCount > 0 )
    {
        ChangeWindowStep2();
        return;
    }

    g_CritialSection.Lock();
    if( status.bHandleN64RenderTexture )
        g_pFrameBufferManager->CloseRenderTexture(true);
    
    g_pFrameBufferManager->SetAddrBeDisplayed(*g_RdpInterface->VI_ORIGIN_REG);

    if( status.gDlistCount == 0 )
    {
        // CPU frame buffer update
        uint32 width = *g_RdpInterface->VI_WIDTH_REG;
        if( (*g_RdpInterface->VI_ORIGIN_REG & (g_dwRamSize-1) ) > width*2 && *g_RdpInterface->VI_H_START_REG != 0 && width != 0 )
        {
            SetVIScales();
            CRender::GetRender()->DrawFrameBuffer(true);
            CGraphicsContext::Get()->UpdateFrame();
        }
        g_CritialSection.Unlock();
        return;
    }

    TXTRBUF_DETAIL_DUMP(TRACE1("VI ORIG is updated to %08X", *g_RdpInterface->VI_ORIGIN_REG));

    if( currentRomOptions.screenUpdateSetting == SCREEN_UPDATE_AT_VI_UPDATE )
    {
        CGraphicsContext::Get()->UpdateFrame();

        DEBUGGER_IF_DUMP( pauseAtNext, TRACE1("Update Screen: VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG));
        DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_FRAME);
        DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_SET_CIMG);
        g_CritialSection.Unlock();
        return;
    }

    TXTRBUF_DETAIL_DUMP(TRACE1("VI ORIG is updated to %08X", *g_RdpInterface->VI_ORIGIN_REG));

    if( currentRomOptions.screenUpdateSetting == SCREEN_UPDATE_AT_VI_UPDATE_AND_DRAWN )
    {
        if( status.bScreenIsDrawn )
        {
            CGraphicsContext::Get()->UpdateFrame();
            DEBUGGER_IF_DUMP( pauseAtNext, TRACE1("Update Screen: VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG));
        }
        else
        {
            DEBUGGER_IF_DUMP( pauseAtNext, TRACE1("Skip Screen Update: VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG));
        }

        DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_FRAME);
        DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_SET_CIMG);
        g_CritialSection.Unlock();
        return;
    }

    if( currentRomOptions.screenUpdateSetting==SCREEN_UPDATE_AT_VI_CHANGE )
    {

        if( *g_RdpInterface->VI_ORIGIN_REG != status.curVIOriginReg )
        {
            if( *g_RdpInterface->VI_ORIGIN_REG < status.curDisplayBuffer || *g_RdpInterface->VI_ORIGIN_REG > status.curDisplayBuffer+0x2000  )
            {
                status.curDisplayBuffer = *g_RdpInterface->VI_ORIGIN_REG;
                status.curVIOriginReg = status.curDisplayBuffer;
                //status.curRenderBuffer = NULL;

                CGraphicsContext::Get()->UpdateFrame();
                DEBUGGER_IF_DUMP( pauseAtNext, TRACE1("Update Screen: VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG));
                DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_FRAME);
                DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_SET_CIMG);
            }
            else
            {
                status.curDisplayBuffer = *g_RdpInterface->VI_ORIGIN_REG;
                status.curVIOriginReg = status.curDisplayBuffer;
                DEBUGGER_PAUSE_AND_DUMP_NO_UPDATE(NEXT_FRAME, {DebuggerAppendMsg("Skip Screen Update, closed to the display buffer, VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG);});
            }
        }
        else
        {
            DEBUGGER_PAUSE_AND_DUMP_NO_UPDATE(NEXT_FRAME, {DebuggerAppendMsg("Skip Screen Update, the same VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG);});
        }

        g_CritialSection.Unlock();
        return;
    }

    if( currentRomOptions.screenUpdateSetting >= SCREEN_UPDATE_AT_1ST_CI_CHANGE )
    {
        status.bVIOriginIsUpdated=true;
        DEBUGGER_PAUSE_AND_DUMP_NO_UPDATE(NEXT_FRAME, {DebuggerAppendMsg("VI ORIG is updated to %08X", *g_RdpInterface->VI_ORIGIN_REG);});
        g_CritialSection.Unlock();
        return;
    }

    DEBUGGER_IF_DUMP( pauseAtNext, TRACE1("VI is updated, No screen update: VIORIG=%08X", *g_RdpInterface->VI_ORIGIN_REG));
    DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_FRAME);
    DEBUGGER_PAUSE_COUNT_N_WITHOUT_UPDATE(NEXT_SET_CIMG);

    g_CritialSection.Unlock();
}

static void ProcessDListStep2(void)
{
    g_CritialSection.Lock();
    if( status.toShowCFB )
    {
        CRender::GetRender()->DrawFrameBuffer(true);
        status.toShowCFB = false;
    }

    try
    {
        DLParser_Process((OSTask *)(g_CoreInterface->DMEM + 0x0FC0));
    }
    catch (...)
    {
        TRACE0("Unknown Error in ProcessDList");
        TriggerDPInterrupt();
        TriggerSPInterrupt();
    }

    g_CritialSection.Unlock();
}   

extern "C" void StartVideo(void)
{
    windowSetting.dps = windowSetting.fps = -1;
    windowSetting.lastSecDlistCount = windowSetting.lastSecFrameCount = 0xFFFFFFFF;

    g_CritialSection.Lock();

    memcpy(&g_curRomInfo.romheader, g_CoreInterface->HEADER, sizeof(ROMHeader));
    unsigned char *puc = (unsigned char *) &g_curRomInfo.romheader;
    unsigned int i;
    unsigned char temp;
    for (i = 0; i < sizeof(ROMHeader); i += 4) /* byte-swap the ROM header */
    {
        temp     = puc[i];
        puc[i]   = puc[i+3];
        puc[i+3] = temp;
        temp     = puc[i+1];
        puc[i+1] = puc[i+2];
        puc[i+2] = temp;
    }

    ROM_GetRomNameFromHeader(g_curRomInfo.szGameName, &g_curRomInfo.romheader);
    Ini_GetRomOptions(&g_curRomInfo);
    char *p = (char *) g_curRomInfo.szGameName + (strlen((char *) g_curRomInfo.szGameName) -1);     // -1 to skip null
    while (p >= (char *) g_curRomInfo.szGameName)
    {
        if( *p == ':' || *p == '\\' || *p == '/' )
            *p = '-';
        p--;
    }

    GenerateCurrentRomOptions();
    status.dwTvSystem = CountryCodeToTVSystem(g_curRomInfo.romheader.nCountryID);
    if( status.dwTvSystem == TV_SYSTEM_NTSC )
        status.fRatio = 0.75f;
    else
        status.fRatio = 9/11.0f;;
    
    InitExternalTextures();

    try {
        CDeviceBuilder::GetBuilder()->CreateGraphicsContext();
        CGraphicsContext::InitWindowInfo();

        bool res = CGraphicsContext::Get()->Initialize(640, 480, !windowSetting.bDisplayFullscreen);
        if (!res)
        {
            g_CritialSection.Unlock();
            return;
        }
        CDeviceBuilder::GetBuilder()->CreateRender();
        CRender::GetRender()->Initialize();
        DLParser_Init();
        status.bGameIsRunning = true;
    }
    catch(...)
    {
        DebugMessage(M64MSG_ERROR, "Exception caught while starting video renderer");
        throw 0;
    }
   
    g_CritialSection.Unlock();
}

static void StopVideo()
{
    g_CritialSection.Lock();
    status.bGameIsRunning = false;

    try {
        CloseExternalTextures();

        // Kill all textures?
        gTextureManager.RecycleAllTextures();
        gTextureManager.CleanUp();
        RDP_Cleanup();

        CDeviceBuilder::GetBuilder()->DeleteRender();
        CGraphicsContext::Get()->CleanUp();
        CDeviceBuilder::GetBuilder()->DeleteGraphicsContext();
        }
    catch(...)
    {
        TRACE0("Some exceptions during RomClosed");
    }

    g_CritialSection.Unlock();
    windowSetting.dps = windowSetting.fps = -1;
    windowSetting.lastSecDlistCount = windowSetting.lastSecFrameCount = 0xFFFFFFFF;
    status.gDlistCount = status.gFrameCount = 0;

}

void SetVIScales()
{
    if( g_curRomInfo.VIHeight>0 && g_curRomInfo.VIWidth>0 )
    {
        windowSetting.fViWidth = windowSetting.uViWidth = g_curRomInfo.VIWidth;
        windowSetting.fViHeight = windowSetting.uViHeight = g_curRomInfo.VIHeight;
    }
    else if( g_curRomInfo.UseCIWidthAndRatio && g_CI.dwWidth )
    {
        windowSetting.fViWidth = windowSetting.uViWidth = g_CI.dwWidth;
        windowSetting.fViHeight = windowSetting.uViHeight = 
            g_curRomInfo.UseCIWidthAndRatio == USE_CI_WIDTH_AND_RATIO_FOR_NTSC ? g_CI.dwWidth/4*3 : g_CI.dwWidth/11*9;
    }
    else
    {
        float xscale, yscale;
        uint32 val = *g_RdpInterface->VI_X_SCALE_REG & 0xFFF;
        xscale = (float)val / (1<<10);
        uint32 start = *g_RdpInterface->VI_H_START_REG >> 16;
        uint32 end = *g_RdpInterface->VI_H_START_REG&0xFFFF;
        uint32 width = *g_RdpInterface->VI_WIDTH_REG;
        windowSetting.fViWidth = (end-start)*xscale;
        if( abs((int)(windowSetting.fViWidth - width) ) < 8 ) 
        {
            windowSetting.fViWidth = (float)width;
        }
        else
        {
            DebuggerAppendMsg("fViWidth = %f, Width Reg=%d", windowSetting.fViWidth, width);
        }

        val = (*g_RdpInterface->VI_Y_SCALE_REG & 0xFFF);// - ((*g_RdpInterface->VI_Y_SCALE_REG>>16) & 0xFFF);
        if( val == 0x3FF )  val = 0x400;
        yscale = (float)val / (1<<10);
        start = *g_RdpInterface->VI_V_START_REG >> 16;
        end = *g_RdpInterface->VI_V_START_REG&0xFFFF;
        windowSetting.fViHeight = (end-start)/2*yscale;

        if( yscale == 0 )
        {
            windowSetting.fViHeight = windowSetting.fViWidth*status.fRatio;
        }
        else
        {
            if( *g_RdpInterface->VI_WIDTH_REG > 0x300 ) 
                windowSetting.fViHeight *= 2;

            if( windowSetting.fViWidth*status.fRatio > windowSetting.fViHeight && (*g_RdpInterface->VI_X_SCALE_REG & 0xFF) != 0 )
            {
                if( abs(int(windowSetting.fViWidth*status.fRatio - windowSetting.fViHeight)) < 8 )
                {
                    windowSetting.fViHeight = windowSetting.fViWidth*status.fRatio;
                }
                /*
                else
                {
                    if( abs(windowSetting.fViWidth*status.fRatio-windowSetting.fViHeight) > windowSetting.fViWidth*0.1f )
                    {
                        if( status.fRatio > 0.8 )
                            windowSetting.fViHeight = windowSetting.fViWidth*3/4;
                        //windowSetting.fViHeight = (*g_RdpInterface->VI_V_SYNC_REG - 0x2C)/2;
                    }
                }
                */
            }
            
            if( windowSetting.fViHeight<100 || windowSetting.fViWidth<100 )
            {
                //At sometime, value in VI_H_START_REG or VI_V_START_REG are 0
                windowSetting.fViWidth = (float)*g_RdpInterface->VI_WIDTH_REG;
                windowSetting.fViHeight = windowSetting.fViWidth*status.fRatio;
            }
        }

        windowSetting.uViWidth = (unsigned short)(windowSetting.fViWidth/4);
        windowSetting.fViWidth = windowSetting.uViWidth *= 4;

        windowSetting.uViHeight = (unsigned short)(windowSetting.fViHeight/4);
        windowSetting.fViHeight = windowSetting.uViHeight *= 4;
        uint16 optimizeHeight = (uint16)(windowSetting.uViWidth*status.fRatio);
        optimizeHeight &= ~3;

        uint16 optimizeHeight2 = (uint16)(windowSetting.uViWidth*3/4);
        optimizeHeight2 &= ~3;

        if( windowSetting.uViHeight != optimizeHeight && windowSetting.uViHeight != optimizeHeight2 )
        {
            if( abs(windowSetting.uViHeight-optimizeHeight) <= 8 )
                windowSetting.fViHeight = windowSetting.uViHeight = optimizeHeight;
            else if( abs(windowSetting.uViHeight-optimizeHeight2) <= 8 )
                windowSetting.fViHeight = windowSetting.uViHeight = optimizeHeight2;
        }


        if( gRDP.scissor.left == 0 && gRDP.scissor.top == 0 && gRDP.scissor.right != 0 )
        {
            if( (*g_RdpInterface->VI_X_SCALE_REG & 0xFF) != 0x0 && gRDP.scissor.right == windowSetting.uViWidth )
            {
                // Mario Tennis
                if( abs(int( windowSetting.fViHeight - gRDP.scissor.bottom )) < 8 )
                {
                    windowSetting.fViHeight = windowSetting.uViHeight = gRDP.scissor.bottom;
                }
                else if( windowSetting.fViHeight < gRDP.scissor.bottom )
                {
                    windowSetting.fViHeight = windowSetting.uViHeight = gRDP.scissor.bottom;
                }
                windowSetting.fViHeight = windowSetting.uViHeight = gRDP.scissor.bottom;
            }
            else if( gRDP.scissor.right == windowSetting.uViWidth - 1 && gRDP.scissor.bottom != 0 )
            {
                if( windowSetting.uViHeight != optimizeHeight && windowSetting.uViHeight != optimizeHeight2 )
                {
                    if( status.fRatio != 0.75 && windowSetting.fViHeight > optimizeHeight/2 )
                    {
                        windowSetting.fViHeight = windowSetting.uViHeight = gRDP.scissor.bottom + gRDP.scissor.top + 1;
                    }
                }
            }
            else if( gRDP.scissor.right == windowSetting.uViWidth && gRDP.scissor.bottom != 0  && status.fRatio != 0.75 )
            {
                if( windowSetting.uViHeight != optimizeHeight && windowSetting.uViHeight != optimizeHeight2 )
                {
                    if( status.fRatio != 0.75 && windowSetting.fViHeight > optimizeHeight/2 )
                    {
                        windowSetting.fViHeight = windowSetting.uViHeight = gRDP.scissor.bottom + gRDP.scissor.top + 1;
                    }
                }
            }
        }
    }
    SetScreenMult(windowSetting.uDisplayWidth/windowSetting.fViWidth, windowSetting.uDisplayHeight/windowSetting.fViHeight);
}

void TriggerDPInterrupt(void)
{
    *(g_CoreInterface->MI_INTR_REG) |= MI_INTR_DP;
    g_CoreInterface->CheckInterrupts();
}

void TriggerSPInterrupt(void)
{
    *(g_CoreInterface->MI_INTR_REG) |= MI_INTR_SP;
    g_CoreInterface->CheckInterrupts();
}

void _VIDEO_DisplayTemporaryMessage(const char *Message)
{
}

void DebugMessage(int level, const char *message, ...)
{
  char msgbuf[1024];
  va_list args;

  if (l_DebugCallback == NULL)
      return;

  va_start(args, message);
  vsprintf(msgbuf, message, args);

  (*l_DebugCallback)(l_DebugCallContext, level, msgbuf);

  va_end(args);
}

extern "C" void InitVideo()
{
	memset(&status, 0, sizeof(status));
    g_pRDRAMu8          = g_CoreInterface->RDRAM;
    g_pRDRAMu32         = (uint32*)g_CoreInterface->RDRAM;
    g_pRDRAMs8          = (signed char *)g_CoreInterface->RDRAM;

    windowSetting.fViWidth = 320;
    windowSetting.fViHeight = 240;
    status.ToToggleFullScreen = FALSE;
    status.bDisableFPS=false;

    if (!InitConfiguration())
    {
        DebugMessage(M64MSG_ERROR, "Failed to read configuration data");
		return;
    }

    CGraphicsContext::InitWindowInfo();
    CGraphicsContext::InitDeviceParameters();


	/* Read RiceVideoLinux.ini file, set up internal variables by reading values from core configuration API */
    LoadConfiguration();

	g_RspInterface->ProcessDlist = ProcessDList;
	g_RspInterface->ProcessRdpList = ProcessRDPList;
	g_RdpInterface->ShowCFB = ShowCFB;
	g_RdpInterface->ChangeWindow = ChangeWindow;
	g_RdpInterface->FBGetFrameBufferInfo = FBGetFrameBufferInfo;
	g_RdpInterface->FBRead = FBRead;
	g_RdpInterface->FBWrite = FBWrite;
	g_RdpInterface->MoveScreen = MoveScreen;
	g_RdpInterface->ReadScreen2 = ReadScreen2;
	g_RdpInterface->SetRenderingCallback = SetRenderingCallback;
	g_RdpInterface->UpdateScreen = UpdateScreen;
	g_RdpInterface->ViStatusChanged = ViStatusChanged;
	g_RdpInterface->ViWidthChanged = ViWidthChanged;

    if( g_CritialSection.IsLocked() )
    {
        g_CritialSection.Unlock();
        TRACE0("g_CritialSection is locked when game is starting, unlock it now.");
    }
    status.bDisableFPS=false;

   g_dwRamSize = 0x800000;
    
#ifdef DEBUGGER
    if( debuggerPause )
    {
        debuggerPause = FALSE;
        usleep(100 * 1000);
    }
#endif
}

extern "C" void ShutdownVideo()
{
    TRACE0("To stop video");
    Ini_StoreRomOptions(&g_curRomInfo);
    StopVideo();
    TRACE0("Video is stopped");
}

// since these functions are exported, they need to have C-style names
#ifdef __cplusplus
extern "C" {
#endif

/* Mupen64Plus plugin functions */

void ChangeWindow (void)
{
    if( status.ToToggleFullScreen )
        status.ToToggleFullScreen = FALSE;
    else
        status.ToToggleFullScreen = TRUE;
}

void MoveScreen (int xpos, int ypos)
{ 
}

void UpdateScreen(void)
{
        static unsigned int lastTick=0;
        static int frames=0;
        unsigned int nowTick = SDL_GetTicks();
        frames++;
        if(lastTick + 5000 <= nowTick)
        {
            char caption[200];
            sprintf(caption, "%s - %.3f VI/S", "Video OGL", frames/5.0);
            CoreVideo_SetCaption(caption);
            frames = 0;
            lastTick = nowTick;
        }

    UpdateScreenStep2();
}

void ViStatusChanged(void)
{
    g_CritialSection.Lock();
    SetVIScales();
    CRender::g_pRender->UpdateClipRectangle();
    g_CritialSection.Unlock();
}

void ViWidthChanged(void)
{
    g_CritialSection.Lock();
    SetVIScales();
    CRender::g_pRender->UpdateClipRectangle();
    g_CritialSection.Unlock();
}

void ProcessRDPList(void)
{
    try
    {
        RDP_DLParser_Process();
    }
    catch (...)
    {
        TRACE0("Unknown Error in ProcessRDPList");
        TriggerDPInterrupt();
        TriggerSPInterrupt();
    }
}   

void ProcessDList(void)
{
    ProcessDListStep2();
}   

/******************************************************************
  Function: FrameBufferRead
  Purpose:  This function is called to notify the dll that the
            frame buffer memory is beening read at the given address.
            DLL should copy content from its render buffer to the frame buffer
            in N64 RDRAM
            DLL is responsible to maintain its own frame buffer memory addr list
            DLL should copy 4KB block content back to RDRAM frame buffer.
            Emulator should not this function again if other memory
            is read within the same 4KB range

            Since depth buffer is also being watched, the reported addr
            may belong to depth buffer
  input:    addr        rdram address
            val         val
            size        1 = uint8, 2 = uint16, 4 = uint32
  output:   none
*******************************************************************/ 
void FBRead(uint32 addr)
{
    g_pFrameBufferManager->FrameBufferReadByCPU(addr);
}

/******************************************************************
  Function: FrameBufferWrite
  Purpose:  This function is called to notify the dll that the
            frame buffer has been modified by CPU at the given address.

            Since depth buffer is also being watched, the reported addr
            may belong to depth buffer

  input:    addr        rdram address
            val         val
            size        1 = uint8, 2 = uint16, 4 = uint32
  output:   none
*******************************************************************/ 
void FBWrite(uint32 addr, uint32 size)
{
    g_pFrameBufferManager->FrameBufferWriteByCPU(addr, size);
}

/************************************************************************
Function: FBGetFrameBufferInfo
Purpose:  This function is called by the emulator core to retrieve frame
          buffer information from the video plugin in order to be able
          to notify the video plugin about CPU frame buffer read/write
          operations

          size:
            = 1     byte
            = 2     word (16 bit) <-- this is N64 default depth buffer format
            = 4     dword (32 bit)

          when frame buffer information is not available yet, set all values
          in the FrameBufferInfo structure to 0

input:    FrameBufferInfo pinfo[6]
          pinfo is pointed to a FrameBufferInfo structure which to be
          filled in by this function
output:   Values are return in the FrameBufferInfo structure
          Plugin can return up to 6 frame buffer info
 ************************************************************************/

void FBGetFrameBufferInfo(void *p)
{
    FrameBufferInfo * pinfo = (FrameBufferInfo *)p;
    memset(pinfo,0,sizeof(FrameBufferInfo)*6);

    //if( g_ZI.dwAddr == 0 )
    //{
    //  memset(pinfo,0,sizeof(FrameBufferInfo)*6);
    //}
    //else
    {
        for (int i=0; i<5; i++ )
        {
            if( status.gDlistCount-g_RecentCIInfo[i].lastUsedFrame > 30 || g_RecentCIInfo[i].lastUsedFrame == 0 )
            {
                //memset(&pinfo[i],0,sizeof(FrameBufferInfo));
            }
            else
            {
                pinfo[i].addr = g_RecentCIInfo[i].dwAddr;
                pinfo[i].size = 2;
                pinfo[i].width = g_RecentCIInfo[i].dwWidth;
                pinfo[i].height = g_RecentCIInfo[i].dwHeight;
                TXTRBUF_DETAIL_DUMP(TRACE3("Protect 0x%08X (%d,%d)", g_RecentCIInfo[i].dwAddr, g_RecentCIInfo[i].dwWidth, g_RecentCIInfo[i].dwHeight));
                pinfo[5].width = g_RecentCIInfo[i].dwWidth;
                pinfo[5].height = g_RecentCIInfo[i].dwHeight;
            }
        }

        pinfo[5].addr = g_ZI.dwAddr;
        //pinfo->size = g_RecentCIInfo[5].dwSize;
        pinfo[5].size = 2;
        TXTRBUF_DETAIL_DUMP(TRACE3("Protect 0x%08X (%d,%d)", pinfo[5].addr, pinfo[5].width, pinfo[5].height));
    }
}

void ShowCFB(void)
{
    status.toShowCFB = true;
}

void ReadScreen2(void *dest, int *width, int *height, int bFront)
{
    if (width == NULL || height == NULL)
        return;

    *width = windowSetting.uDisplayWidth;
    *height = windowSetting.uDisplayHeight;
   
    if (dest == NULL)
        return;
   
    GLint oldMode;
    glGetIntegerv( GL_READ_BUFFER, &oldMode );
    if (bFront)
        glReadBuffer( GL_FRONT );
    else
        glReadBuffer( GL_BACK );
    glReadPixels( 0, 0, windowSetting.uDisplayWidth, windowSetting.uDisplayHeight,
                 GL_RGB, GL_UNSIGNED_BYTE, dest );
    glReadBuffer( oldMode );
}
    
void SetRenderingCallback(void (*callback)(int))
{
    renderCallback = callback;
}

void SetDisplaySize(int width, int height)
{
	windowSetting.uDisplayWidth = (unsigned int)width;
	windowSetting.uDisplayHeight = (unsigned int)height;
}

#ifdef __cplusplus
}
#endif

